1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.graphics.g2d.geometrybatch;
12 import hip.graphics.orthocamera;
13 import hip.hiprenderer.renderer;
14 import hip.hiprenderer.shader;
15 import hip.error.handler;
16 import hip.graphics.mesh;
17 import hip.math.matrix;
18 import hip.math.utils;
19 import hip.math.vector;
20 public import hip.api.graphics.color;
21 public import hip.api.graphics.batch;
22 
23 
24 enum defaultColor = HipColor.white;
25 
26 @HipShaderInputLayout struct HipGeometryBatchVertex
27 {
28     import hip.math.vector;
29     Vector3 vPosition;
30     // @HipShaderInputPadding float __padding = 0;
31     HipColor vColor = HipColor.white;
32 
33     static enum floatCount = HipGeometryBatchVertex.sizeof / float.sizeof;
34 }
35 
36 @HipShaderVertexUniform("Geom")
37 struct HipGeometryBatchVertexUniforms
38 {
39     Matrix4 uModel = Matrix4.identity;
40     Matrix4 uView = Matrix4.identity;
41     Matrix4 uProj = Matrix4.identity;
42 }
43 
44 @HipShaderFragmentUniform("FragVars")
45 struct HipGeometryBatchFragmentUniforms
46 {
47     float[4] uGlobalColor = [1,1,1,1];
48 }
49 
50 /**
51 *   This class uses the vertex layout XYZ RGBA.
52 *   it is meant to be a 2D API for drawing primitives
53 */
54 class GeometryBatch : IHipBatch
55 {
56     protected Mesh mesh;
57     protected index_t lastIndexDrawn;
58     protected index_t lastVertexDrawn;
59     protected index_t currentIndex;
60     protected index_t verticesCount;
61     protected index_t indicesCount;
62     protected HipColor currentColor;
63 
64     float managedDepth = 0;
65     HipOrthoCamera camera;
66     HipGeometryBatchVertex[] vertices;
67     index_t[] indices;
68 
69     
70     this(HipOrthoCamera camera = null, index_t verticesCount=64_000, index_t indicesCount=64_000)
71     {
72         Shader s = HipRenderer.newShader(HipShaderPresets.GEOMETRY_BATCH); 
73         s.addVarLayout(ShaderVariablesLayout.from!HipGeometryBatchVertexUniforms);
74         s.addVarLayout(ShaderVariablesLayout.from!HipGeometryBatchFragmentUniforms);
75         s.setBlending(HipBlendFunction.SRC_ALPHA, HipBlendFunction.ONE_MINUS_SRC_ALPHA, HipBlendEquation.ADD);
76 
77 
78         mesh = new Mesh(HipVertexArrayObject.getVAO!HipGeometryBatchVertex, s);
79         vertices = new HipGeometryBatchVertex[verticesCount];
80         indices = new index_t[indicesCount];
81         indices[] = 0;
82         //Initialize the mesh with 0
83         mesh.createVertexBuffer(verticesCount, HipBufferUsage.DYNAMIC);
84         mesh.createIndexBuffer(indicesCount, HipBufferUsage.DYNAMIC);
85         mesh.vao.bind();
86         mesh.setIndices(indices);
87         mesh.setVertices(vertices);
88         mesh.sendAttributes();
89         this.setColor(defaultColor);
90 
91         if(camera is null)
92             camera = new HipOrthoCamera();
93         this.camera = camera;
94 
95     }
96 
97     protected pragma(inline) void checkVerticesCount(int howMuch)
98     {
99         if(verticesCount+howMuch >= this.vertices.length/HipGeometryBatchVertex.floatCount)
100         {
101             import hip.util.string;
102             String s = String("Too many vertices ", verticesCount+howMuch, " for a buffer of size ", 
103                 this.vertices.length/HipGeometryBatchVertex.floatCount
104             );
105             ErrorHandler.assertExit(false, s.toString);
106         }
107     }
108 
109     void setCurrentDepth(float depth){managedDepth = depth;}
110 
111 
112     /**
113     * Adds a vertex to the structure and return its current index.
114     */
115     index_t addVertex(float x, float y, float z)
116     {
117         if(currentColor.a == 0) return verticesCount;
118         vertices[verticesCount] = HipGeometryBatchVertex(
119             Vector3(x,y,z),
120             currentColor
121         );
122         return verticesCount++;
123     }
124     
125     void addIndex(index_t[] newIndices ...)
126     {
127         if(currentColor.a == 0) return;
128         if(currentIndex+newIndices.length >= this.indices.length)
129         {
130             import hip.util.string;
131             String s = String("Too many indices ", currentIndex+1, " for a buffer of size ", this.indices.length);
132             ErrorHandler.assertExit(false, s.toString);
133         }
134         foreach(index; newIndices)
135             indices[currentIndex++] = index;
136     }
137     void setColor(HipColor c)
138     {
139         assert(c != HipColor.no, "Can't use 'no' color on geometry batch");
140         currentColor = c;
141     }
142 
143     protected void triangleVertices(int x1, int y1, int x2, int y2, int x3, int y3)
144     {
145         checkVerticesCount(3);
146         addVertex(x1, y1, managedDepth);
147         addVertex(x2, y2, managedDepth);
148         addVertex(x3, y3, managedDepth);
149         addIndex(
150             cast(index_t)(verticesCount-3),
151             cast(index_t)(verticesCount-2),
152             cast(index_t)(verticesCount-1)
153         );
154     }
155 
156 
157     protected void fillEllipseVertices(int x, int y, int radiusW, int radiusH, int degrees, int startDegrees ,int precision)
158     {
159         // assert(precision >= 3, "Can't have a circle with less than 3 vertices");
160 
161         //Normalize the precision for iterating it on the loop,
162         //Multiply by degrees * DEG_TO_RAD
163         float angle_mult = (1.0/precision) * (degrees) * (PI/180.0);
164 
165         float startAngle = (PI/180.0) * startDegrees;
166 
167         checkVerticesCount(2);
168         index_t centerIndex = addVertex(x, y, managedDepth);
169         //The first vertex
170         index_t lastVert = addVertex(x + radiusW*cos(startAngle), y + radiusH*sin(startAngle), managedDepth);
171         index_t firstVert = lastVert;
172         
173         checkVerticesCount(precision);
174         for(int i = 0; i < precision; i++)
175         {
176             //Divide degrees for the total iterations
177             float nextAngle = (i+1)*angle_mult + startAngle;
178 
179             //Use a temporary variable to hold the new lastVert for more performance
180             //on addIndex calls
181             index_t tempNewLastVert = addVertex(x+radiusW*cos(nextAngle), y + radiusH*sin(nextAngle), managedDepth);
182             
183             addIndex(
184                 centerIndex, //Puts the center first
185                 lastVert, //Appends the vertex from the last iteration
186                 tempNewLastVert//Appends the next vertex
187             );
188             //Updates the last iteration with the next vertex
189             lastVert = tempNewLastVert;
190         }
191 
192         addIndex(
193             centerIndex,
194             lastVert,
195             firstVert
196         );
197     }
198 
199 
200 
201     void drawEllipse(int x, int y, int radiusW, int radiusH, int degrees = 360, HipColor color = HipColor.no, int precision = 24)
202     {
203         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
204         if(HipRenderer.getMode != HipRendererMode.LINE)
205         {
206             flush();
207             HipRenderer.setRendererMode(HipRendererMode.LINE);
208         }
209         float angle_mult = (1.0/precision) * degrees * (PI/180.0);
210         checkVerticesCount(1);
211         index_t currVert = addVertex(x+ radiusW*cos(0.0), y + radiusH*sin(0.0), managedDepth);
212         index_t firstVert = currVert;
213 
214         checkVerticesCount(precision);
215         for(int i = 1; i < precision+1; i++)
216         {
217             float nextAngle = angle_mult * i;
218             index_t tempNextVert = addVertex(x + radiusW * cos(nextAngle), y + radiusH*sin(nextAngle), managedDepth);
219 
220             addIndex(currVert, tempNextVert);
221             currVert = tempNextVert;
222         }
223 
224         addIndex(firstVert, currVert);
225         setColor(oldColor);
226     }
227 
228     private HipColor setColorIfChangedAndGetOldColor(in HipColor color)
229     {
230         HipColor oldColor = currentColor;
231         if(color != HipColor.no)
232             setColor(color);
233         return oldColor;
234     }
235 
236     ///With this default precision, the circle should be smooth enough
237     void fillEllipse(int x, int y, int radiusW, int radiusH = -1, int degrees = 360, HipColor color = HipColor.no, int precision = 24)
238     {
239         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
240         if(radiusH == -1)
241             radiusH = radiusW;
242         if(HipRenderer.getMode != HipRendererMode.TRIANGLES)
243         {
244             flush();
245             HipRenderer.setRendererMode(HipRendererMode.TRIANGLES);
246         }
247         fillEllipseVertices(x, y, radiusW, radiusH, degrees, 0, precision);
248         setColor(oldColor);
249     }
250 
251     void fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3, HipColor color = HipColor.no)
252     {
253         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
254         if(HipRenderer.getMode != HipRendererMode.TRIANGLES)
255         {
256             flush();
257             HipRenderer.setRendererMode(HipRendererMode.TRIANGLES);
258         }
259         triangleVertices(x1,y1,x2,y2,x3,y3);
260         setColor(oldColor);
261     }
262     void drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, HipColor color = HipColor.no)
263     {
264         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
265         if(HipRenderer.getMode != HipRendererMode.LINE_STRIP)
266         {
267             flush();
268             HipRenderer.setRendererMode(HipRendererMode.LINE_STRIP);
269         }
270         triangleVertices(x1, y1, x2, y2, x3, y3);
271         setColor(oldColor);
272     }
273     
274     void drawLine(int x1, int y1, int x2, int y2, HipColor color = HipColor.no)
275     {
276         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
277         if(HipRenderer.getMode != HipRendererMode.LINE)
278         {
279             flush();
280             HipRenderer.setRendererMode(HipRendererMode.LINE);
281         }
282         checkVerticesCount(2);
283         addVertex(x1, y1, managedDepth);
284         addVertex(x2, y2, managedDepth);
285 
286         addIndex(
287             cast(index_t)(verticesCount-2),
288             cast(index_t)(verticesCount-1)
289         );
290         setColor(oldColor);
291     }
292 
293     void drawLine(float x1, float y1, float x2, float y2, HipColor color = HipColor.no)
294     {
295         drawLine(
296             cast(int)x1,
297             cast(int)y1,
298             cast(int)x2,
299             cast(int)y2,
300             color
301         );
302     }
303 
304     void drawQuadraticBezierLine(int x0, int y0, int x1, int y1, int x2, int y2, int precision=24, HipColor color = HipColor.no)
305     {
306         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
307 
308         Vector2 last = Vector2(x0, y0);
309 
310         float precisionMultiplier = 1.0f/precision;
311 
312         for(int i = 0; i <= precision; i++)
313         {
314             float t = cast(float)i*precisionMultiplier;
315             float tNext = t+precisionMultiplier;
316             Vector2 bz = quadraticBezier(x0, y0, x1, y1, x2, y2, tNext);
317             drawLine(last.x, last.y, bz.x, bz.y);
318             last = bz;
319         }
320         drawLine(last.x, last.y, x2, y2);
321         setColor(oldColor);
322     }
323 
324     void drawPixel(int x, int y, HipColor color = HipColor.no)
325     {
326         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
327         if(HipRenderer.getMode != HipRendererMode.POINT)
328         {
329             flush();
330             HipRenderer.setRendererMode(HipRendererMode.POINT);
331         }
332         checkVerticesCount(1);
333         addVertex(x, y, managedDepth);
334         addIndex(verticesCount);
335         setColor(oldColor);
336     }
337 
338     /**
339     *   Draws the following rectangle scheme:
340     *  0 _______ 3
341     *   |       |
342     *   |       |
343     *   |_______|
344     *  1        2
345     *   0, 1, 2
346     *   2, 3, 0
347     */
348     pragma(inline, true)
349     protected void rectangleVertices(int x, int y, int w, int h)
350     {
351         checkVerticesCount(4);
352         index_t topLeft = addVertex(x, y, managedDepth);
353         index_t botLeft = addVertex(x, y+h, managedDepth);
354         index_t botRight= addVertex(x+w, y+h, managedDepth);
355         index_t topRight= addVertex(x+w, y, managedDepth);
356  
357         addIndex(
358             topLeft, botLeft, botRight,
359             botRight, topRight, topLeft
360         );
361 
362     }
363 
364     void drawRectangle(int x, int y, int w, int h, HipColor color = HipColor.no)
365     {
366         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
367         if(HipRenderer.getMode != HipRendererMode.LINE_STRIP)
368         {
369             flush();
370             HipRenderer.setRendererMode(HipRendererMode.LINE_STRIP);
371         }
372         rectangleVertices(x,y,w,h);
373         setColor(oldColor);
374     }
375 
376     void fillRoundRect(int x, int y, int w, int h, int radius = 4, HipColor color = HipColor.no, int vertices = 16)
377     {
378         if(radius == 0)
379             return fillRectangle(x,y,w,h,color);
380         if(HipRenderer.getMode != HipRendererMode.TRIANGLES)
381         {
382             flush();
383             HipRenderer.setRendererMode(HipRendererMode.TRIANGLES);
384         }
385         int vPerEdge = vertices/4;
386         int r2 = radius*2;
387         HipColor old = setColorIfChangedAndGetOldColor(color);
388 
389         ///Draw internal rect.
390         rectangleVertices(x+radius, y+radius, w-r2, h-r2);
391 
392         ///Draw ellipses and also draw border rects
393         //Top Left
394         fillEllipseVertices(x+radius, y+radius, radius, radius, 90, 180, vPerEdge);
395         rectangleVertices(x+radius, y, w - r2, radius);
396         //Top Right
397         fillEllipseVertices(x+w-radius, y+radius, radius, radius, 90, 270, vPerEdge);
398         rectangleVertices(x+w-radius, y+radius, radius, h-r2);
399         // Bottom Right
400         fillEllipseVertices(x+w-radius, y+h-radius, radius, radius, 90, 0, vPerEdge);
401         rectangleVertices(x+radius, y+h-radius, w-r2, radius);
402         //Bottom Left
403         fillEllipseVertices(x+radius, y+h-radius, radius, radius, 90, 90, vPerEdge);
404         rectangleVertices(x, y+radius, radius, h-r2);
405 
406         setColor(old);
407     }
408   
409 
410     void fillRectangle(int x, int y, int w, int h, HipColor color = HipColor.no)
411     {
412         HipColor oldColor = setColorIfChangedAndGetOldColor(color);
413         if(HipRenderer.getMode != HipRendererMode.TRIANGLES)
414         {
415             flush();
416             HipRenderer.setRendererMode(HipRendererMode.TRIANGLES);
417         }
418         rectangleVertices(x,y,w,h);
419         setColor(oldColor);
420     }
421 
422     void draw()
423     {
424         const uint count = this.currentIndex;
425         import hip.console.log;
426 
427         if(count - lastIndexDrawn != 0)
428         {
429             mesh.bind();
430             mesh.updateVertices(cast(float[])vertices[lastVertexDrawn..verticesCount], lastVertexDrawn);
431             mesh.updateIndices(indices[lastIndexDrawn..currentIndex], lastIndexDrawn);
432 
433             mesh.shader.setFragmentVar("FragVars.uGlobalColor", cast(float[4])[1,1,1,1], true);
434             mesh.shader.setVertexVar("Geom.uProj",  camera.proj, true);
435             mesh.shader.setVertexVar("Geom.uModel", Matrix4.identity(), true);
436             mesh.shader.setVertexVar("Geom.uView",  camera.view, true);
437 
438             mesh.shader.sendVars();
439             //Vertices to render = indices.length
440             this.mesh.draw(count - lastIndexDrawn, HipRenderer.getMode, lastIndexDrawn);
441             mesh.unbind();
442         }
443         lastIndexDrawn = count;
444         lastVertexDrawn = verticesCount;
445     }
446 
447     void flush()
448     {
449         draw();
450         lastVertexDrawn = verticesCount = 0;
451         lastIndexDrawn = currentIndex = 0;
452     }
453 
454 }